package commons.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import commons.cloud.MachineType;


public class ConsumptionAnalyser {
	
	private static final String LIGHT_PROVIDER = "Light";
	private static final String MEDIUM_PROVIDER = "Medium";
	private static final String HEAVY_PROVIDER = "Heavy";
	
	protected static HashMap<String, Map<MachineType, Double>> reservationFees;
	protected static HashMap<MachineType, Double> onDemandFees;
	protected static HashMap<String, Map<MachineType, Double>> reservedFees;
	protected static HashMap<String, Map<MachineType, Double>> minimumConsumption;
	protected static Map<String, Map<MachineType, Map<Long, Double>>> reservedMarketUsagePerType;
	protected static Map<MachineType, Map<Long, Double>> onDemandMarketUsagePerType;
	protected static double monitoringFee;
	
	protected static double totalDemand;
	protected static double cost;
//	private static HashMap<String, Map<MachineType, Long>> originallyReserved;
	
	public static void main(String[] args) {
		
		if(args.length != 1){
			System.err.println("Usage: <consumption file>");
			System.exit(1);
		}
		
		initValues();
		readMachineUsage(args[0]);
		
		try {
			FileWriter writer = new FileWriter(new File("acc_cons.output"));
			writer.write("provider"+"\t"+"type"+"\t"+"machines"+"\t"+"hours"+"\n");
			
			//Accumulating instances usage
			for(String provider : reservedMarketUsagePerType.keySet()){
				Map<MachineType, Map<Long, Double>> currentProviderUsage = reservedMarketUsagePerType.get(provider);
				Map<MachineType, Map<Long, Double>> currentProviderAccumulated = updateConsumption(currentProviderUsage);
				
//				Map<MachineType, Long> providerReserved = originallyReserved.get(provider);

				for(Entry<MachineType, Map<Long, Double>> entry : currentProviderAccumulated.entrySet()){
					MachineType machineType = entry.getKey();
					Map<Long, Double> usage = entry.getValue();
					
//					Long totalReserved = providerReserved.get(machineType);
					
					for(Long amountOfMachines : usage.keySet()){
						if(amountOfMachines != 0){
							writer.write(provider+"\t"+machineType.toString()+"\t"+amountOfMachines+"\t"+(usage.get(amountOfMachines) / (8760))+"\n");
						}
					}
				}
			}
			
			//Accumulating instances usage for on-demand market
			Map<MachineType, Map<Long, Double>> currentProviderUsage = onDemandMarketUsagePerType;
			Map<MachineType, Map<Long, Double>> currentProviderAccumulated = updateConsumption(currentProviderUsage);
			
			for(Entry<MachineType, Map<Long, Double>> entry : currentProviderAccumulated.entrySet()){
				MachineType machineType = entry.getKey();
				Map<Long, Double> usage = entry.getValue();
				
				for(Long amountOfMachines : usage.keySet()){
					if(amountOfMachines != 0){
						writer.write("amazon_l_on"+"\t"+machineType.toString()+"\t"+amountOfMachines+"\t"+(usage.get(amountOfMachines) / (8760))+"\n");
					}
				}
			}
			
			writer.close();
		} catch (IOException e) {
		}
		
	}
	
	/**
	 * This method retrieves machine consumption per machine type during the simulation
	 * @param file
	 * @return
	 */
	protected static void readMachineUsage(String file) {
		//Input format: Provider: + provider.getName();Reserved:reservedResources;OnDemand:onDemandResources
		
		//Reading machine consumption from file
		try {
			BufferedReader reader = new BufferedReader(new FileReader(new File(file)));
			while(reader.ready()){
				String[] split = reader.readLine().split(";");//Splitting by ;
				
				String provider = split[0];
				String reservedResources = split[1].split(":")[1];
				String onDemandResources = split[2].split(":")[1];
				
				String reservedMap = reservedResources.substring(1, reservedResources.length()-1);
				String onDemandMap = onDemandResources.substring(1, onDemandResources.length()-1);
				
				Map<MachineType, Long> currentReservationLine = new HashMap<MachineType, Long>();
				Map<MachineType, Long> currentOnDemandLine = new HashMap<MachineType, Long>();
				
				//Reading reserved resources usage map
				if(!reservedMap.isEmpty()){
					String[] reservations = reservedMap.split(",");
					for(int  i = 0; i < reservations.length; i++){
						String[] data = reservations[i].split("=");
						MachineType currentType = MachineType.valueOf(data[0].trim());
						
						currentReservationLine.put(currentType, Long.valueOf(data[1].trim()));
					}
				}
				
				//Updating reserved resources usage per provider market and type
				Map<MachineType, Map<Long, Double>> providerUsage = reservedMarketUsagePerType.get(provider);
				if(providerUsage == null){
					providerUsage = new HashMap<MachineType, Map<Long,Double>>();
				}
				for(Entry<MachineType, Long> entry : currentReservationLine.entrySet()){
					MachineType currentType = entry.getKey();
					Long currentAmountOfMachines = entry.getValue();
					
					Map<Long, Double> currentTypeUsage = providerUsage.get(currentType);
					if(currentTypeUsage == null){
						currentTypeUsage = new HashMap<Long, Double>();
					}
					Double usage = currentTypeUsage.get(currentAmountOfMachines);
					if(usage == null){
						usage = new Double(0.0);
					}
					currentTypeUsage.put(currentAmountOfMachines, usage+1);
					providerUsage.put(currentType, currentTypeUsage);
				}
				reservedMarketUsagePerType.put(provider, providerUsage);
				
				//Reading on-demand resources usage map
				if(!onDemandMap.isEmpty()){
					String[] consumption = onDemandMap.split(",");
					for(int  i = 0; i < consumption.length; i++){
						String[] data = consumption[i].split("=");
						MachineType currentType = MachineType.valueOf(data[0].trim());
						
						currentOnDemandLine.put(currentType, Long.valueOf(data[1].trim()));
					}
				}
				
				for(Entry<MachineType, Long> entry : currentOnDemandLine.entrySet()){
					MachineType currentType = entry.getKey();
					Long currentAmountOfMachines = entry.getValue();
					
					Map<Long, Double> currentTypeUsage = onDemandMarketUsagePerType.get(currentType);
					if(currentTypeUsage == null){
						currentTypeUsage = new HashMap<Long, Double>();
					}
					Double usage = currentTypeUsage.get(currentAmountOfMachines);
					if(usage == null){
						usage = new Double(0.0);
					}
					currentTypeUsage.put(currentAmountOfMachines, usage+1);
					onDemandMarketUsagePerType.put(currentType, currentTypeUsage);
				}
			}
			reader.close();
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	private static Map<MachineType, Map<Long, Double>> updateConsumption(Map<MachineType, Map<Long, Double>> hoursUsedPerType) {
		
		Map<MachineType, Map<Long, Double>> cumulativeHoursUsedPerType = new HashMap<MachineType, Map<Long,Double>>();
		
		for(MachineType type : hoursUsedPerType.keySet()){
			
			Map<Long, Double> hoursUsed = hoursUsedPerType.get(type);
			
			//Updating cumulative consumption
			ArrayList<Long> numberOfMachinesList = new ArrayList<Long>(hoursUsed.keySet());
			Collections.sort(numberOfMachinesList);
			Collections.reverse(numberOfMachinesList);
			
			Map<Long, Double> newUsageData = new HashMap<Long, Double>();
			newUsageData.put(numberOfMachinesList.get(0), hoursUsed.get(numberOfMachinesList.get(0)));//First value
			
			for(Long numberOfMachinesUsed : numberOfMachinesList){//Iterating from the highest number of machines to the lowest one
				double higherUsed = hoursUsed.get(numberOfMachinesUsed);
				
				for(long i = numberOfMachinesUsed - 1; i > 0; i--){//If 15 machines were used for 10 hours, 10 machines were also used for 10 hours ...
					Double lowerUsed = (newUsageData.get(i) != null) ? newUsageData.get(i) : hoursUsed.get(i);
					
					if(lowerUsed != null){
						newUsageData.put(i, lowerUsed+higherUsed);
					}
				}
			}
			
			cumulativeHoursUsedPerType.put(type, newUsageData);
		}
		
		
		return cumulativeHoursUsedPerType;
	}
	
	protected static void initValues() {
		
//		originallyReserved = new HashMap<String, Map<MachineType, Long>>();
//		HashMap<MachineType, Long> amazonLReservation = new HashMap<MachineType, Long>();
//		amazonLReservation.put(MachineType.M1_SMALL, 1l);
//		amazonLReservation.put(MachineType.M1_LARGE, 1l);
//		amazonLReservation.put(MachineType.M1_XLARGE, 1l);
//		HashMap<MachineType, Long> amazonMReservation = new HashMap<MachineType, Long>();
//		amazonMReservation.put(MachineType.M1_SMALL, 1l);
//		amazonMReservation.put(MachineType.M1_LARGE, 1l);
//		amazonMReservation.put(MachineType.M1_XLARGE, 1l);
//		HashMap<MachineType, Long> amazonHReservation = new HashMap<MachineType, Long>();
//		amazonHReservation.put(MachineType.M1_SMALL, 1l);
//		amazonHReservation.put(MachineType.M1_LARGE, 1l);
//		amazonHReservation.put(MachineType.M1_XLARGE, 1l);
//		originallyReserved.put("amazon_l", amazonLReservation);
//		originallyReserved.put("amazon_m", amazonMReservation);
//		originallyReserved.put("amazon_h", amazonHReservation);
		
		reservationFees = new HashMap<String, Map<MachineType,Double>>();
		
		Map<MachineType, Double> lightProvider = new HashMap<MachineType, Double>();
		lightProvider.put(MachineType.M1_SMALL, 61.0);
		lightProvider.put(MachineType.M1_LARGE, 243.0);
		lightProvider.put(MachineType.M1_XLARGE, 486.0);
		reservationFees.put(LIGHT_PROVIDER, lightProvider);
		
		Map<MachineType, Double> mediumProvider = new HashMap<MachineType, Double>();
		mediumProvider.put(MachineType.M1_SMALL, 139.0);
		mediumProvider.put(MachineType.M1_LARGE, 554.0);
		mediumProvider.put(MachineType.M1_XLARGE, 1108.0);
		reservationFees.put(MEDIUM_PROVIDER, mediumProvider);
		
		//High utilization instances are charged for a whole year usage
		Map<MachineType, Double> highProvider = new HashMap<MachineType, Double>();
		highProvider.put(MachineType.M1_SMALL, 169 + 8760 * 0.014);
		highProvider.put(MachineType.M1_LARGE, 676 + 8760 * 0.056);
		highProvider.put(MachineType.M1_XLARGE, 1352 + 8760 * 0.112);
		reservationFees.put(HEAVY_PROVIDER, highProvider);
		
		//On-demand fees are the same for all providers
		onDemandFees = new HashMap<MachineType, Double>();
		onDemandFees.put(MachineType.M1_SMALL, 0.06);
		onDemandFees.put(MachineType.M1_LARGE, 0.24);
		onDemandFees.put(MachineType.M1_XLARGE, 0.48);
		
		//Reserved instances usage fees
		reservedFees = new HashMap<String, Map<MachineType,Double>>();

		Map<MachineType, Double> lightProviderUsage = new HashMap<MachineType, Double>();
		lightProviderUsage.put(MachineType.M1_SMALL, 0.034);
		lightProviderUsage.put(MachineType.M1_LARGE, 0.136);
		lightProviderUsage.put(MachineType.M1_XLARGE, 0.271);
		reservedFees.put(LIGHT_PROVIDER, lightProviderUsage);
		
		Map<MachineType, Double> mediumProviderUsage = new HashMap<MachineType, Double>();
		mediumProviderUsage.put(MachineType.M1_SMALL, 0.021);
		mediumProviderUsage.put(MachineType.M1_LARGE, 0.084);
		mediumProviderUsage.put(MachineType.M1_XLARGE, 0.168);
		reservedFees.put(MEDIUM_PROVIDER, mediumProviderUsage);
		
		//High utilization market resources do not have usage fee since they're charged for the whole year usage
		Map<MachineType, Double> highProviderUsage = new HashMap<MachineType, Double>();
		highProviderUsage.put(MachineType.M1_SMALL, 0.0);
		highProviderUsage.put(MachineType.M1_LARGE, 0.0);
		highProviderUsage.put(MachineType.M1_XLARGE, 0.0);
		reservedFees.put(HEAVY_PROVIDER, highProviderUsage);
		
		monitoringFee = 0.005;

		minimumConsumption = new HashMap<String, Map<MachineType,Double>>();
		
		//Minimum Consumption for Light provider
		Map<MachineType, Double> lightMinimumConsumption =  new HashMap<MachineType, Double>();
		double smallConsumption = reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL) / (onDemandFees.get(MachineType.M1_SMALL) - reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_SMALL));
		lightMinimumConsumption.put(MachineType.M1_SMALL, smallConsumption);
		
		double largeConsumption = reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE) / (onDemandFees.get(MachineType.M1_LARGE) - reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_LARGE));
		lightMinimumConsumption.put(MachineType.M1_LARGE, largeConsumption);
		
		double xLargeConsumption = reservationFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE) / (onDemandFees.get(MachineType.M1_XLARGE) - reservedFees.get(LIGHT_PROVIDER).get(MachineType.M1_XLARGE));
		lightMinimumConsumption.put(MachineType.M1_XLARGE, xLargeConsumption);
		minimumConsumption.put(LIGHT_PROVIDER, lightMinimumConsumption);
		
		//Minimum Consumption for Medium provider
		Map<MachineType, Double> mediumMinimumConsumption =  new HashMap<MachineType, Double>();
		smallConsumption = reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL) / (onDemandFees.get(MachineType.M1_SMALL) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_SMALL));
		mediumMinimumConsumption.put(MachineType.M1_SMALL, smallConsumption);
		
		largeConsumption = reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE) / (onDemandFees.get(MachineType.M1_LARGE) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_LARGE));
		mediumMinimumConsumption.put(MachineType.M1_LARGE, largeConsumption);
		
		xLargeConsumption = reservationFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE) / (onDemandFees.get(MachineType.M1_XLARGE) - reservedFees.get(MEDIUM_PROVIDER).get(MachineType.M1_XLARGE));
		mediumMinimumConsumption.put(MachineType.M1_XLARGE, xLargeConsumption);
		minimumConsumption.put(MEDIUM_PROVIDER, mediumMinimumConsumption);
				
		//Minimum Consumption for High provider
		Map<MachineType, Double> highMinimumConsumption =  new HashMap<MachineType, Double>();
		smallConsumption = reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL) / (onDemandFees.get(MachineType.M1_SMALL) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_SMALL));
		highMinimumConsumption.put(MachineType.M1_SMALL, smallConsumption);
		
		largeConsumption = reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE) / (onDemandFees.get(MachineType.M1_LARGE) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_LARGE));
		highMinimumConsumption.put(MachineType.M1_LARGE, largeConsumption);
		
		xLargeConsumption = reservationFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE) / (onDemandFees.get(MachineType.M1_XLARGE) - reservedFees.get(HEAVY_PROVIDER).get(MachineType.M1_XLARGE));
		highMinimumConsumption.put(MachineType.M1_XLARGE, xLargeConsumption);
		minimumConsumption.put(HEAVY_PROVIDER, highMinimumConsumption);
		
		totalDemand = 0;
		cost = 0;
		
		reservedMarketUsagePerType = new HashMap<String, Map<MachineType, Map<Long, Double>>>();
		onDemandMarketUsagePerType = new HashMap<MachineType, Map<Long,Double>>();
	}
}
